package com.limetalks;

/*
 * RED5 Open Source Flash Server - http://www.osflash.org/red5
 * 
 * Copyright (c) 2006-2008 by respective authors (see below). All rights reserved.
 * 
 * This library is free software; you can redistribute it and/or modify it under the 
 * terms of the GNU Lesser General Public License as published by the Free Software 
 * Foundation; either version 2.1 of the License, or (at your option) any later 
 * version. 
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY 
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A 
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along 
 * with this library; if not, write to the Free Software Foundation, Inc., 
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

import org.red5.server.adapter.ApplicationAdapter;
import org.red5.server.adapter.MultiThreadedApplicationAdapter;
import org.red5.server.api.IClient;
import org.red5.server.api.IConnection;
import org.red5.server.api.IScope;
import org.red5.server.api.Red5;
import org.red5.server.api.event.IEvent;
import org.red5.server.api.event.IEventListener;
import org.red5.server.api.service.ServiceUtils;

import com.limetalks.dao.StreamDAO;
import com.limetalks.dao.UserDAO;
import com.limetalks.data.Stream;
import com.limetalks.data.Stream.StreamStatus;
import com.limetalks.data.User;
import com.limetalks.util.GlobalStrings;
import com.limetalks.util.SessionTracker;

/**
 * Sample application that uses the client manager.
 * 
 * @author The Red5 Project (red5@osflash.org)
 */
public class Application1 extends MultiThreadedApplicationAdapter implements IEventListener {

	private EntityManagerFactory emf;
	private UserDAO userDAO;
	private StreamDAO streamDAO;
	private Set<Stream> streams = new HashSet<Stream>();
	private Set<User> users = new HashSet<User>();
	private Map<IClient, User> clients = new HashMap<IClient, User>();
	SessionTracker sessionTracker = SessionTracker.instance();
	
	private IScope videoScope;
	private IScope sphereScope;

	public Application1() {

		super();
		if (emf == null) {
			emf = Persistence.createEntityManagerFactory("LimeTalksUnit");
			userDAO = new UserDAO(emf);
			streamDAO = new StreamDAO(emf);
		}
	}

	@Override
	public boolean appStart(IScope app) {
		System.err.println("appStart contextPath=" + app.getContextPath()
				+ "; name=" + app.getName());
		 // setup aphere scope
	    if (!app.createChildScope(GlobalStrings.SPHERE_SCOPE_NAME))
	        return false;
	    sphereScope = app.getScope(GlobalStrings.SPHERE_SCOPE_NAME);
	    sphereScope.addEventListener(this);

	    // setup video scope
	    if (!app.createChildScope(GlobalStrings.VIDEO_SCOPE_NAME))
	        return false;
	    videoScope = app.getScope(GlobalStrings.VIDEO_SCOPE_NAME);
	    videoScope.addEventListener(this);
		return true;
	}

	@Override
	public boolean appConnect(IConnection conn, Object[] params) {
		System.err.println("appConnect conn.host=" + conn.getHost()
				+ "; conn.path=" + conn.getPath());
		return super.appConnect(conn, params);
	}

	/** {@inheritDoc} */
	@Override
	public boolean connect(IConnection conn, IScope scope, Object[] params) {
		System.err.println("connect conn.host=" + conn.getHost()
				+ "; conn.path=" + conn.getPath() + "; scope.contextPath="
				+ scope.getContextPath() + ";");
		// login if sessionId was passed
		if (scope.getContextPath().startsWith(
				"/" + GlobalStrings.VIDEO_SCOPE_NAME + "/")
				&& params[0] != null) {
			String sessionId = params[0].toString();
			login_Server(conn, sessionId);
		}
		return super.connect(conn, scope, params);
	}

	/** {@inheritDoc} */
	@Override
	public void disconnect(IConnection conn, IScope scope) {
		System.err.println("disconnect conn.host=" + conn.getHost()
				+ "; conn.path=" + conn.getPath() + "; scope.contextPath="
				+ scope.getContextPath() + ";");
		super.disconnect(conn, scope);
	}

	@Override
	public synchronized boolean start(IScope scope) {
		System.err.println("start contextPath=" + scope.getContextPath()
				+ "; name=" + scope.getName());
		return super.start(scope);
	}

	@Override
	public boolean roomStart(IScope roomScope) {
		System.err.println("roomStart contextPath="
				+ roomScope.getContextPath() + "; name=" + roomScope.getName());
		boolean result = super.roomStart(roomScope);
		if (result) {
			if (isStream(roomScope)) {
				startStream(Integer.parseInt(roomScope.getName()));
			}
		}
		return result;
	}

	@Override
	public synchronized void stop(IScope scope) {
		System.err.println("stop contextPath=" + scope.getContextPath()
				+ "; name=" + scope.getName());
		super.stop(scope);
	}

	@Override
	public void roomStop(IScope roomScope) {
		System.err.println("roomStop contextPath=" + roomScope.getContextPath()
				+ "; name=" + roomScope.getName());
		if (isStream(roomScope)) {
			stopStream(Integer.parseInt(roomScope.getName()));
		}
		super.roomStop(roomScope);
	}

	@Override
	public synchronized boolean join(IClient client, IScope scope) {
		// System.err.println("join client.id=" + client.getId() +
		// " scope.contextPath=" + scope.getContextPath() + "; scope.name=" +
		// scope.getName());
		return super.join(client, scope);
	}

	@Override
	public boolean roomJoin(IClient client, IScope scope) {
		System.err.println("roomJoin client.id=" + client.getId()
				+ " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName());
		boolean result = super.roomJoin(client, scope);

		if (result) {
			if (isStream(scope)) {
				// if(scope.getName().equals(GlobalStrings.sphereScopeName)) {
				// sphereApp client joined room
				// } else
				// if(scope.getName().equals(GlobalStrings.VIDEO_SCOPE_NAME)) {

				User user = getUserByClient(client);
				Integer streamId = Integer.parseInt(scope.getName());
				Stream stream = getStream(streamId);

				// somehow returns
				IConnection con = (IConnection) client.getConnections(scope)
						.toArray()[0];

				// join stream
				stream.addUser(user);
				ServiceUtils.invokeOnAllConnections(scope,
						"onStreamUpdated_Client", new Object[] { stream });
				ServiceUtils.invokeOnConnection(con, "onStreamJoin_Client",
						new Object[] { user, stream });
			}
		}
		return result;
	}

	@Override
	public synchronized void leave(IClient client, IScope scope) {
		System.err.println("leave client.id=" + client.getId()
				+ " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName());
		super.leave(client, scope);
	}

	@Override
	public void roomLeave(IClient client, IScope scope) {
		System.err.println("roomLeave client.id=" + client.getId()
				+ " scope.contextPath=" + scope.getContextPath()
				+ "; scope.name=" + scope.getName());
		super.roomLeave(client, scope);
		
		if (isStream(scope)) {

			User user = getUserByClient(client);
			Integer streamId = Integer.parseInt(scope.getName());// check
			Stream stream = getStream(streamId);

			// leave stream
			stream.removeUser(user);
			ServiceUtils.invokeOnAllConnections(scope,
					"onStreamUpdated_Client", new Object[] { stream });
			ServiceUtils.invokeOnConnection("onStreamLeave_Client",
					new Object[] { user, stream });
		}
	}

	public void login_Server(IConnection conn, String sessionId) {
		System.err.println("login_Server sessionId: " + sessionId);
		// IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		User user = sessionTracker.getUserBySessionId(sessionId);
		// User user = new User();
		// user.setId(Integer.parseInt(sessionId));//temp stub
		
		if (user == null) {
			
			user = new User();
			user.setName("Guest");
			user.setId(0);
			user.setGuest(true);
		}
			clients.put(client, user);
			ServiceUtils.invokeOnConnection(conn, "onUserLogin_Client",
					new Object[] { user });
			
			//ServiceUtils.invokeOnConnection(conn, "onUserLoginError_Client",
					//new Object[] {});
	}

	public void logout_Server() {
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		clients.remove(client.getId());
		ServiceUtils.invokeOnAllConnections(conn.getScope(),
				"onUserLogout_Client", new Object[] {});
	}

	public boolean startAndJoinStream_Server(Integer streamId) {
		IScope scope = getChildScope(streamId.toString());
		if (scope == null) {
			if (!startStream_Server(streamId)) {
				return false;
			}
		}
		return joinStream_Server(streamId);
	}

	public boolean startStream_Server(Integer streamId) {
		boolean result = createChildScope(streamId.toString());
		return result;
	}

	private void startStream(Integer streamId) {
		System.err.println("startStream streamId= " + streamId);
		Stream stream = streamDAO.findById(streamId);
		// temp stub
		// Stream stream = new Stream();
		// stream.setId(streamId);
		// temp stub
		
		if(stream != null){
			streams.add(stream);
			// client call:a new room is created
	
			// ServiceUtils.invokeOnAllConnections(scope1,//getScope(),
			// "onStreamStarted_Client", new Object[] { stream });
			// boolean newScope = createChildScope(GlobalStrings.SPHERE_SCOPE_NAME +
			// "/" + streamId);
			// System.err.println("newScope=  " + newScope);
	
			// calling onStreamStart on sphere
			IScope scope = getChildScope(GlobalStrings.SPHERE_SCOPE_NAME);
			if (scope != null)
				ServiceUtils.invokeOnAllConnections(scope,
						"onStreamStarted_Client", new Object[] { stream });
		}
	}

	public void stopStream_Server(Integer streamId) {
		IScope roomScope = getChildScope(streamId.toString());
		removeChildScope(roomScope);
	}

	private void stopStream(Integer streamId) {
		System.err.println("stopStream streamId= " + streamId);
		Stream stream = getStream(streamId);
		streams.remove(stream);
		stream.setStatus(StreamStatus.PAST);
		try{
			streamDAO.save(stream);
		}catch (Exception e) {
			e.printStackTrace();
		}
	
		ServiceUtils.invokeOnAllConnections(sphereScope,
			"onStreamStopped_Client", new Object[] { stream });
	}

	public boolean joinStream_Server(Integer streamId) {
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		IScope roomScope = getChildScope(streamId.toString());
		// getScope().disconnect(conn); // leave root
		// return conn.connect(roomScope); // connect to the stream
		return join(client, roomScope);
	}

	public void leaveStream_Server(Integer streamId) {
		IScope roomScope = getChildScope(streamId.toString());
		IConnection conn = Red5.getConnectionLocal();
		IClient client = conn.getClient();
		// roomScope.disconnect(conn); // leave the room
		// conn.connect(getScope()); // connect to root
		super.leave(client, roomScope);
	}

	private User getUser(Integer userId) {
		User result = null;
		for (User user : users) {
			if (user.getId().equals(userId)) {
				result = user;
				break;
			}
		}
		return result;
	}

	private User getUserByClient(IClient client) {
		return (User) (clients.get(client));
	}

	private Stream getStream(Integer streamId) {
		Stream result = null;
		for (Stream stream : streams) {
			if (stream.getId().equals(streamId)) {
				result = stream;
				break;
			}
		}
		return result;
	}

	public Set<Stream> getStreams_Server() {
		return streams;
	}
	
	/*private Set<Stream> getStreamsByCategory(Integer categoryId) {
		
		Set<Stream> streamByCategory =  new HashSet<Stream>();
		for (Stream stream : streams)
			if(stream.getCategory().getId() == categoryId)
				streamByCategory.add(stream);
				
		System.err.println("getStreamsByCategory("+categoryId+"): ");
		for (Stream stream : streamByCategory)
			System.err.println(stream.getName() + ", " + stream.getId());
				
		return streamByCategory;
	}*/

	private boolean isStream(IScope scope) {
		return scope.getName().matches(GlobalStrings.STARTS_WITH_DIGIT);
	}
	
	public void chatSendMessage_Server(IConnection conn, String message) {
		System.err.println("chatSendMessage_Server message= " + message);
		User user = (User)(clients.get(conn.getClient()));
		ServiceUtils.invokeOnAllConnections(conn.getScope(), "onChatMessageReceive_Client", new Object[] {user, message});
	}
	
	@Override
	public void notifyEvent(IEvent arg0) {
		System.err.println("notifyEvent");
	}
}
